#include "logger.h"

Logger::Logger() {
  _log_entries = NULL;
  _logs = 0;
  verbose("New instance of logger was created.");
}

Logger::~Logger() {
  verbose("Logging finished.");
  outputLogs(_log_entries);
  delete[] _log_entries;
}

Logger* Logger::getInstance() {
  static Logger instance;
  return &instance;
}

void Logger::verbose(char* message, char* tag) {
  log(message, tag, kVerbose);
}

void Logger::info(char* message, char* tag) {
  log(message, tag, kInfo);
}

void Logger::debug(char* message, char* tag) {
  log(message, tag, kDebug);
}

void Logger::warn(char* message, char* tag) {
  log(message, tag, kDebug);
}

void Logger::error(char* message, char* tag) {
  log(message, tag, kError);
}

void Logger::wtf(char* message, char* tag) {
  log(message, tag, kAssert);
}

void Logger::log(char* message, char* tag, const int priority) {
  LogEntry log_entry;
  log_entry.time = getCurrentTime();
  log_entry.message = message;
  log_entry.tag = tag;
  log_entry.priority = priority;
  _log_entries = (LogEntry*)realloc(_log_entries, (++_logs) * sizeof(LogEntry));
  _log_entries[_logs - 1] = log_entry;
}

char* Logger::getPriorityText(const int priority) {
  char* priority_text = (char*)malloc(8);
  switch(priority) {
  case kVerbose:
    priority_text = "[VERB] ";
    break;
  case kInfo:
    priority_text = "[INFO] ";
    break;
  case kDebug:
    priority_text = "[DEBUG]";
    break;
  case kWarn:
    priority_text = "[WARN] ";
    break;
  case kError:
    priority_text = "[ERROR]";
    break;
  case kAssert:
    priority_text = "[FATAL]";
    break;
  default:
    priority_text = "[-----]";
    break;
  }
  return priority_text;
}

char* Logger::getCurrentTime() {
  time_t t = time(0);   // get time now
  tm * now = localtime(&t);
  const int kDateSize = 20;
  char* result = (char*)malloc(kDateSize);
  sprintf(result, "%d.%02d.%02d %02d:%02d:%02d", now->tm_year + 1900,
          now->tm_mon + 1, now->tm_mday, now->tm_hour, now->tm_min,
          now->tm_sec);
  return result;
}

char* Logger::getLogString(LogEntry log_entry) {
  char* log_string = (char*)malloc(MAX_LOG_SIZE);
  sprintf(log_string, "%s  %s  %s Tag: %s", log_entry.time,
          getPriorityText(log_entry.priority), log_entry.message,
          log_entry.tag);
  return log_string;
}

Logger::LogEntry* Logger::getLogs(int minimal_priority,
                                        char* tag_filter) {
  bool no_priority_filter = minimal_priority <= 1;
  bool no_tag_filter = tag_filter == NULL || tag_filter == "";
  if (no_priority_filter && no_tag_filter)
    return _log_entries;
  
  if (minimal_priority > kAssert) {
    minimal_priority = kAssert;
  }
  LogEntry* result = NULL;
  int result_count = 0;
  // Output logs matching filters into the new array
  for (int i = 0; i < _logs; i++) {
    LogEntry log_entry = _log_entries[i];
    if (log_entry.priority >= minimal_priority && (no_tag_filter ||
                                    strstr(log_entry.tag, tag_filter))) {
      result = (LogEntry*)realloc(&result, (++result_count) * sizeof(LogEntry));
      result[result_count - 1] = log_entry;
    }
  }
  return result;
}

char** Logger::getLogStrings(LogEntry* log_entries, int* size) {
  char** result = NULL;
  int result_count = 0;
  for (int i = 0; i < _logs; i++) {
    char* log_string = getLogString(log_entries[i]);
    result = (char**)realloc(result, (++result_count) * sizeof(char*));
    result[result_count - 1] = log_string;
  }
  *size = result_count;
  return result;
}

void Logger::outputLogs(LogEntry* log_entries, wchar_t* filepath) {
  int size = 0;
  char** log_strings = getLogStrings(log_entries, &size);
  // Free memory if outputting only a part of all logs
  if (log_entries != _log_entries) {
    delete[] log_entries;
  }
  char* buffer = new char;
  int length = 0;
  // Copy log strings into the buffer
  for (int i = 0; i < size; i++) {
    char* output = log_strings[i];
    int log_length = strlen(output) + 1;
    length += log_length;
    buffer = (char*)realloc(buffer, length);
    memcpy(buffer + length - log_length , output, log_length - 1);
  }
  // TODO: add newline after each log
  // Mapping buffer into log file
  FileIO* file_io = new FileIO(filepath, true, 0, length);
  char* map = file_io->getNextMap();
  memcpy(map, buffer, length);

  // Memory deallocating
  delete[] buffer;
  for (int i = 0; i < size; i++) {
    delete[] log_strings[i];
  }
  delete[] log_strings;
  UnmapViewOfFile(map);
  delete file_io;
}